package sk.ownage.p1.featurestore;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;

import android.os.Environment;
import android.util.Log;

import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MarkerOptions;

public class LandmarkQuadTreeRoot {
	final String TAG = "LandmarkQuadTreeRoot";
	final LatLngBounds boundingBox = new LatLngBounds(
			new LatLng(-85.0, -180.0), new LatLng(85.0, 179.999999));
	static final int threshold = /* 1024 */1024;
	static final int DIRECTORY_MAX = LandmarkStore.DIRECTORY_MAX;
	static final String rootPath = Environment.getExternalStorageDirectory()
			+ "/geometryStore" + "/landmarks";
	public LandmarkQuadTreeNode northWest, northEast, southWest, southEast;
	public LatLng NW, SE, center;
	public LatLng last_landmark;
	String path, exactpath;
	static int actualThreshold = threshold;
	int size = 0;
	boolean divided = false;
	File landmarksListFile;
	boolean makeNew = false;
	boolean skip = false;
	LatLng last_location = null;
	String name;
	double[] minDistances = { 80.0, 3.0, 0.24, 0.06, 0.00001 };
	boolean udpate_geom = false;
	long last_landmark_timestamp = 0;
	public long last_readed_landmark_time = 0;
	public long old_geometry_last_landmark = 0;
	LandmarkReader lr;
	int zoomLevel = 0;

	public LandmarkQuadTreeRoot(int landmark_id, String _path ,  int _zoomLevel) {
		name = "root";
		zoomLevel=_zoomLevel;
		NW = new LatLng(boundingBox.northeast.latitude,
				boundingBox.southwest.longitude);
		SE = new LatLng(boundingBox.southwest.latitude,
				boundingBox.northeast.longitude);
		center = getCenter(boundingBox);
		path = _path + zoomLevel;
		exactpath = path + "/" + "list.bin";
		size = loadTreeState();
		last_landmark = loadLastLandmark();
		old_geometry_last_landmark = last_landmark_timestamp;
		File geomSize = new File(path + "/size.bin");

		File folder = new File(path);
		if (!geomSize.exists()) {
			makeNew = true;
			actualThreshold = threshold;
		}
		// ak v tomto koreni este nebola prekrocena hranica(threshold)
		if (!skip) {
			if (!folder.exists()) {
				folder.mkdirs();
			}

			landmarksListFile = new File(exactpath);
			if (!landmarksListFile.exists())
				try {
					FileOutputStream fos = new FileOutputStream(
							landmarksListFile);
					ObjectOutputStream os = new ObjectOutputStream(fos);
					os.close();
				} catch (FileNotFoundException e) {
					Log.e(TAG, e.toString());
					e.printStackTrace();
				} catch (IOException e) {
					Log.e(TAG, e.toString());
					e.printStackTrace();
				}
		}
		// ak uz existuju vetvy tohoto korena
		else {
			northEast = new LandmarkQuadTreeNode(name + ">NE",
					new LatLngBounds(center, boundingBox.northeast), path
							+ "/0");
			northWest = new LandmarkQuadTreeNode(name + ">NW",
					new LatLngBounds(new LatLng(center.latitude,
							boundingBox.southwest.longitude), new LatLng(
							boundingBox.northeast.latitude, center.longitude)),
					path + "/1");
			southEast = new LandmarkQuadTreeNode(name + ">SE",
					new LatLngBounds(new LatLng(boundingBox.southwest.latitude,
							center.longitude), new LatLng(center.latitude,
							boundingBox.northeast.longitude)), path + "/2");
			southWest = new LandmarkQuadTreeNode(name + ">SW",
					new LatLngBounds(boundingBox.southwest, center), path
							+ "/3");
		}

	}

	private LatLng getCenter(LatLngBounds bbox) {
		double averageLat = (bbox.northeast.latitude + bbox.southwest.latitude) / 2;
		double averageLon = (bbox.northeast.longitude + bbox.southwest.longitude) / 2;
		return new LatLng(averageLat, averageLon);
	}

	/* funkcia sluziaca na vypocet vydialenosti medzi dvomi geografickymi polohami*/
	private double getDistanceBetweenGeopoints(LatLng from, LatLng to) {
		double lat1 = Math.toRadians(from.latitude), lon1 = Math
				.toRadians(from.longitude), lat2 = Math.toRadians(to.latitude), lon2 = Math
				.toRadians(to.longitude);
		double R = 6378.137; 
		double result = (Math.acos(Math.sin(lat1) * Math.sin(lat2)
				+ Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1)) * R);
		return result;
	}

	public void addPointOfInterestToLandmarkGeometry(PointOfInterestsInfo pointOfInterestInfo) {
		this.addPointOfInterestToLandmarkGeometry(new MarkerOptions().position(pointOfInterestInfo
				.getPosition()), pointOfInterestInfo.getTimestamp());
	}

	public void addPointOfInterestToLandmarkGeometry(MarkerOptions input, long timestamp) {
		if (!skip) {
			size = loadTreeState();
			last_readed_landmark_time=last_landmark_timestamp;
		}
		last_landmark = loadLastLandmark();
		if (last_landmark != null){
			if (getDistanceBetweenGeopoints(last_landmark, input.getPosition()) < minDistances[zoomLevel])
				return;
			}
		last_landmark_timestamp=timestamp;
		last_landmark = input.getPosition();
		saveLastLandmark();

		ObjectInputStream is;
		if (size < actualThreshold && !divided) {
			try {
				FileOutputStream fos = new FileOutputStream(landmarksListFile,true);
				AppendingObjectOutputStream os = new AppendingObjectOutputStream(new BufferedOutputStream(fos));
				os.writeDouble(input.getPosition().latitude);
				os.writeDouble(input.getPosition().longitude);
				os.writeUTF((input.getTitle()!=null)?input.getTitle():"");
				os.close();
			} catch (FileNotFoundException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			} catch (IOException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			}
			size++;
			saveTreeState();
		} else if (!divided) {
			try {
				FileInputStream fis = new FileInputStream(landmarksListFile);
				is = new ObjectInputStream(new BufferedInputStream(fis));
				int count = size;
				while (count > 0) {
					MarkerOptions tempMO = new MarkerOptions().position(
							new LatLng(is.readDouble(), is.readDouble()))
							.title(is.readUTF());
					addMarkerOptionToSubTree(tempMO);
					count--;
				}
				is.close();
				addMarkerOptionToSubTree(input);
				landmarksListFile.delete();
			} catch (StreamCorruptedException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			} catch (IOException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			}
			divided = true;
			skip = true;
			saveTreeState();
		} else {
			saveTreeState();
			addMarkerOptionToSubTree(input);
		}
	}

	private void addMarkerOptionToSubTree(MarkerOptions mo) {
		if (mo.getPosition().latitude >= center.latitude) {
			if (mo.getPosition().longitude >= center.longitude) {
				if (northEast == null)
					northEast = new LandmarkQuadTreeNode(name + ">NE",
							new LatLngBounds(center, boundingBox.northeast),
							path + "/0");
				northEast.addMarkerOption(mo);
			} else {
				if (northWest == null)
					northWest = new LandmarkQuadTreeNode(name + ">NW",
							new LatLngBounds(new LatLng(center.latitude,
									boundingBox.southwest.longitude),
									new LatLng(boundingBox.northeast.latitude,
											center.longitude)), path + "/1");
				northWest.addMarkerOption(mo);
			}
		} else {
			if (mo.getPosition().longitude >= center.longitude) {
				if (southEast == null)
					southEast = new LandmarkQuadTreeNode(name + ">SE",
							new LatLngBounds(new LatLng(
									boundingBox.southwest.latitude,
									center.longitude), new LatLng(
									center.latitude,
									boundingBox.northeast.longitude)), path
									+ "/2");
				southEast.addMarkerOption(mo);
			} else {
				if (southWest == null)
					southWest = new LandmarkQuadTreeNode(name + ">SW",
							new LatLngBounds(boundingBox.southwest, center),
							path + "/3");
				southWest.addMarkerOption(mo);
			}
		}
	}

	private LatLng loadLastLandmark() {
		File lastLocationPath = new File(path + "/" + "lastLocation.bin");
		LatLng result = null;
		if (lastLocationPath.exists())
			try {
				FileInputStream fis = new FileInputStream(lastLocationPath);
				ObjectInputStream is = new ObjectInputStream(
						new BufferedInputStream(fis));
				result = new LatLng(is.readDouble(), is.readDouble());
				last_landmark_timestamp = is.readLong();
				is.close();
			} catch (Exception e) {
				Log.d(TAG, e.toString());
			}
		return result;
	}

	private void saveLastLandmark() {
		File lastLocationPath = new File(path + "/" + "lastLocation.bin");
		if (last_landmark != null)
			try {
				FileOutputStream fos = new FileOutputStream(lastLocationPath);
				ObjectOutputStream os = new ObjectOutputStream(
						new BufferedOutputStream(fos));
				os.writeDouble(last_landmark.latitude);
				os.writeDouble(last_landmark.longitude);
				os.writeLong(last_landmark_timestamp);
				os.close();
			} catch (Exception e) {
				Log.d(TAG, e.toString());
			}
	}

	private int loadTreeState() {
		File sizePath = new File(path + "/" + "size.bin");
		int result = 0;
		if (sizePath.exists())
			try {
				FileInputStream fis = new FileInputStream(sizePath);
				ObjectInputStream is = new ObjectInputStream(
						new BufferedInputStream(fis));
				result = is.readInt();
				divided = is.readBoolean();
				skip = is.readBoolean();
				actualThreshold = is.readInt();
				is.close();
			} catch (Exception e) {
				Log.d(TAG, e.toString());
			}
		return result;
	}

	private void saveTreeState() {
		File sizePath = new File(path + "/" + "size.bin");
		try {
			FileOutputStream fos = new FileOutputStream(sizePath);
			ObjectOutputStream os = new ObjectOutputStream(
					new BufferedOutputStream(fos));
			os.writeInt(size);
			os.writeBoolean(divided);
			os.writeBoolean(skip);
			os.writeInt(threshold);
			os.close();
		} catch (Exception e) {
			Log.d(TAG, e.toString());
		}
	}
}
